/*
 * TrackRecord.java
 *
 * Created on 20 juin 2006, 19:38
 *
 * TODO : ecriture d'une ligne log en methode statique
 */

package courselog.gpsutils;

import ewe.io.File;
import ewe.io.FileNotFoundException;
import ewe.io.FileOutputStream;
import ewe.io.FileReader;
import ewe.io.IOException;
import ewe.ui.ReportException;
import ewe.util.Vector;

/**
 * A GPS track.
 * Mainly used to write tracking records into a file, read back those log files
 * into a vector and write them into GPX format.
 * TODO : Read a GPX file?
 *
 * @author rigal
 */
public class TrackRecord {
    
    /** Option to read file in a single track. */
    public static final int OPTION_SINGLETRACK=0;
    
    /** Option to read file in a separate track. */
    public static final int OPTION_SEPARATETRACK=1;
    
    /** Option to read file keeping only the end of the track. */
    public static final int OPTION_ENDTRACK=2;
    
    /**
     * GPS points serie.
     */
    public Vector vPoints;
    
    /**
     * Creates a new instance of TrackRecord
     * empty track.
     */
    public TrackRecord() {
        vPoints=new Vector();
    }
    
    /**
     * Convert a set of Track points into a single track.
     * This recomputes the time between points, the difference
     * in altitudes and the difference in distance to
     * generate the logged values.
     */
    public void makeOneTrack() {
        double refAlt=0.0d;
        
        int nbPt=vPoints.size();
        if(nbPt<1) {
            // Track without a single point!
            return;
        }
        TrackPoint lastPt=(TrackPoint) vPoints.elementAt(0);
        lastPt.durationHere=0.0d;
        lastPt.lengthHere=0.0d;
        lastPt.downwardsHere=0.0d;
        lastPt.upwardsHere=0.0d;
        lastPt.avgSpeed=0.0d;
        lastPt.avgHeading=0.0d;
        refAlt=lastPt.altSeaLevel;
        
        // Follow track and re-built log data
        for(int idx=1; idx<nbPt; idx++) {
            TrackPoint cPt = (TrackPoint) vPoints.elementAt(idx);
            double dur=cPt.timeTo(lastPt);
            cPt.durationHere= dur+lastPt.durationHere;
            double dist=lastPt.distance(cPt);
            cPt.lengthHere = dist+lastPt.lengthHere;
            double diff=cPt.altSeaLevel-refAlt;
            if(Math.abs(diff)>10.0d) {
                if(diff<0.0d) {
                    cPt.downwardsHere = -diff +lastPt.downwardsHere;
                    cPt.upwardsHere=lastPt.upwardsHere;
                }else{
                    cPt.upwardsHere = diff +lastPt.upwardsHere;
                    cPt.downwardsHere=lastPt.downwardsHere;
                }
            }else{
                cPt.downwardsHere=lastPt.downwardsHere;
                cPt.upwardsHere=lastPt.upwardsHere;
            }
            cPt.avgSpeed=3.6d*dist/dur;
            cPt.avgHeading=lastPt.headingTo(cPt);
            
            lastPt=cPt;
        }
    }
    
    /**
     * Recompute tracks points in terms of UTM coordinates from Lat, Lon values.
     */
    public void reCompute() {
        for(int idxEnd=vPoints.size()-1; idxEnd>=0; idxEnd--) {
            GeoPoint obPt= (GeoPoint) vPoints.elementAt(idxEnd);
            obPt.computeLatLonToUTMRef();
        }
    }
    
    /**
     * Invert the direction of this TrackRecord.
     * The vector is in reverse order.
     */
    public void reverse() {
        int idxBegin=0;
        for(int idxEnd=vPoints.size()-1; idxEnd>idxBegin; idxEnd--) {
            Object obPt=vPoints.elementAt(idxEnd);
            vPoints.setElementAt(vPoints.elementAt(idxBegin),idxEnd);
            vPoints.setElementAt(obPt,idxBegin);
            idxBegin++;
        }
        double durationTotal=((TrackPoint) vPoints.elementAt(0)).durationHere;
        for(int idx=vPoints.size()-1; idx>=0; idx--) {
            TrackPoint cPt= (TrackPoint) vPoints.elementAt(idx);
            cPt.durationHere= durationTotal-cPt.durationHere;
        }
    }
    
    /**
     * Convert a TrackRecord consisting in non-consecutive points into set of tracks.
     * This takes as a reference the time between points and assumes that a new track
     * should be generated each time the track point time is back to 0.
     * Current TrackRecord object is unchanged.
     * @return a vector of TrackRecord elements. <B>null</B> if there is only one track
     * in the current track object.
     */
    public Vector makeSplitTracks() {
        
        int nbPt=vPoints.size();
        if(nbPt<=1) {
            // Track without point or with a single point!
            return null;
        }
        Vector vRes = null;
        int idxNew=0;
        // Follow track and find resets of time
        for(int idx=1; idx<nbPt; idx++) {
            TrackPoint cPt = (TrackPoint) vPoints.elementAt(idx);
            if((cPt.durationHere==0)) {
                if(vRes==null) {
                    vRes = new Vector();
                }
                TrackRecord nTr= new TrackRecord();
                for(; idxNew<idx; idxNew++) {
                    nTr.vPoints.add(this.vPoints.elementAt(idxNew));
                }
                vRes.add(nTr);
            }
        }
        if(vRes!=null) {
            TrackRecord nTr= new TrackRecord();
            for(; idxNew<nbPt; idxNew++) {
                nTr.vPoints.add(this.vPoints.elementAt(idxNew));
            }
            vRes.add(nTr);
        }
        return vRes;
    }
    
    /**
     * Read a log file into a new TrackRecord object.
     * Returns <i>null</i> if a read error occured.
     * The whole log file is read into the vector of this track,
     * further post-processing s needed to cut the track into several
     * tracks if the log contains several tracks or the re-compute the
     * points values to generate a continuous track.
     * @param fLog Log file to read.
     * @param tr TrackRecord to fill in (previous points are erased) or null if
     * a new object is to be allocated.
     * @param cutOption flag specifying to make the track a single one, 
     * to leave split tracks or to keep only the end.
     * @return new TrackRecord Object containing TrackPoint objects.
     * @see TrackPoint#readFromLogLine(String)
     */
    public static TrackRecord readLogFile(File fLog, TrackRecord tr,
            int cutOption) {
        
        boolean resetAsked=(cutOption==OPTION_ENDTRACK);
        boolean isAllMeasured=true;
        
        if(tr==null) {
            tr=new TrackRecord();
        }else{
            tr.vPoints.setSize(0);
        }
        
        try {
            FileReader fLogRead = new FileReader(fLog);
            
            String ln;
            double lastDuration=0;
            int idx=0;
            while((ln=fLogRead.readLine())!=null) {
                if(isValidTrackLog(ln)) {
                    TrackPoint cTP;
                    if(idx<tr.vPoints.size()) {
                        cTP= (TrackPoint) tr.vPoints.elementAt(idx);
                    }else{
                        cTP = new TrackPoint();
                        tr.vPoints.add(cTP);
                    }
                    if(cTP.readFromLogLine(ln)) {
                        if(resetAsked&&(cTP.durationHere<lastDuration)) {
                            idx=-1;
                        }
                        lastDuration= cTP.durationHere;
                    }else{
                        return null;
                    }
                }else{
                    isAllMeasured=false;
                    GeoPoint cGeo=new GeoPoint();
                    if(cGeo.readFromLogLine(ln)) {
                        tr.vPoints.add(cGeo);
                    }else{
                        return null;
                    }
                }
                idx++;
            }
            fLogRead.close();
            tr.vPoints.setSize(idx);
            if((isAllMeasured)&&(cutOption==OPTION_SINGLETRACK)) {
                tr.makeOneTrack();
            } 
        } catch (FileNotFoundException ex) {
            new ReportException(ex).execute();
            return null;
        } catch (IOException ex) {
            new ReportException(ex).execute();
            return null;
        }
        
        if(tr.vPoints.size()==0) return null;
        return tr;
    }
    
    /**
     * Check if a string can be interpreted as a TrackPoint log line.
     * The criteria is simply that 15 commas should be present.
     */
    public static boolean isValidTrackLog(String s) {
        int lg=s.length();
        int nbCommas=15;
        for(int idx=0; idx<lg; idx++) {
            if(s.charAt(idx)==',') {
                nbCommas--;
                if(nbCommas==0) return true;
            }
        }
        return false;
    }
    
    /**
     * Writes a TrackRecord object into a log file.
     * Returns <i>false</i> if a write error occured.
     * @param fLog Log file to write.
     * @return true if write operation completed, false otherwise.
     * @see TrackPoint#formatInBuffer(byte[])
     */
    public boolean writeLogFile(File fLog) {
        byte[] buff= new byte[256];
        
        try {
            FileOutputStream fLogWrite = new FileOutputStream(fLog);
            
            int nbPoints=vPoints.size();
            boolean isTrack=true;
            for(int idx=0; idx<nbPoints; idx++) {
                if(!(vPoints.elementAt(idx) instanceof TrackPoint)) {
                    isTrack=false;
                    break;
                }
            }
            int len=0;
            for(int idx=0; idx<nbPoints; idx++) {
                if(isTrack) {
                    TrackPoint cTP = (TrackPoint) vPoints.elementAt(idx);
                    len=cTP.formatInBuffer(buff);
                }else{
                    GeoPoint cTP = (GeoPoint) vPoints.elementAt(idx);
                    len=cTP.formatInBuffer(buff);
                }
                if(len>0) {
                    fLogWrite.write(buff, 0, len);
                }
            }
            fLogWrite.close();
        } catch (FileNotFoundException ex) {
            new ReportException(ex).execute();
            return false;
        } catch (IOException ex) {
            new ReportException(ex).execute();
            return false;
        }
        
        return true;
    }
}
